﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using Kugar.Core.BaseStruct;
using Kugar.Core.Network.Tcp;
using Kugar.Core.ExtMethod;
using Kugar.Core.IO;

namespace Kugar.Core.Communications.FileTransfer
{
    public class FileReceiveManager:IDisposable
    {
        private Socket _socket = null;
        //private MutileKeysDictionary<int, int, FileTransferReceiveTask> cacheConfig = new MutileKeysDictionary<int, int, FileTransferReceiveTask>();
        private Dictionary<int, FileTransferReceiveTask> cacheConfig = new Dictionary<int, FileTransferReceiveTask>();
        private const int DataRevBlockCount = 7800;
        private bool _isOwner = false;
        private bool isDisponsed = false;
        private TimerEx offlineCheckerTimer = null;
        private bool isClose = false;

        private FileReceiveManager()
        {
            this.FileTransferBeginReceive += OnFileTransferBegin;
            offlineCheckerTimer = new TimerEx(lineChecker,20000,null);
        }

        public FileReceiveManager(Socket socket)
            : this(socket, false)
        {
        }

        public FileReceiveManager(Socket socket, bool isOwner):this()
        {
            _socket = socket;
            _isOwner = isOwner;
        }

        public FileReceiveManager(int listenport)
            : this()
        {
            var s = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            s.Bind(new IPEndPoint(IPAddress.Any, listenport));


            s.Listen(1024);

            _socket = s;

            _socket.BeginAccept(socket_acceptConnect, _socket);

            _isOwner = true;

        }

        public void Close()
        {
            if (_isOwner)
            {
                lock (cacheConfig)
                {
                    cacheConfig.Clear();
                }

                offlineCheckerTimer.Stop();

                try
                {
                    if (_socket.Connected)
                    {
                        _socket.Shutdown(SocketShutdown.Both);
                    }
                    
                }
                catch (Exception)
                {
                    
                }

                _socket.Close();

                _socket = null;
            }

            isClose = true;

        }

        public void Start()
        {
            if (isClose)
            {
                _socket.BeginAccept(socket_acceptConnect, _socket);
            }

            isClose = false;
        }


        public string BasePath;

        private void AcceptCallback(IAsyncResult iar)
        {
            var s = _socket.EndAccept(iar);

            var tcp = new TcpClientEx(s, true,DataRevBlockCount);

            tcp.DataReceive += tcpClient_DataReceive;

            if (!isClose)
            {
                _socket.BeginAccept(AcceptCallback, null);
            }
            
        }

        public void Dispose()
        {
            if (!isDisponsed)
            {
                Close();
                isDisponsed = true;
            }
        }

        /// <summary>
        ///     当接收到发送申请时,,引发该事件
        /// </summary>
        public event EventHandler<FileReceiveRequestEventArgs> FileReceiveRequest;

        /// <summary>
        ///     当接收到新数据的时候,引发该事件
        /// </summary>
        public event EventHandler FileReceiveProcessing;

        public event EventHandler<FileTransfer_AllCompletedEventArgs> TransferAllCompleted;

        public event EventHandler<FileTransfer_FileCompletedEventArgs> TransferSingleCompleted;

        public event EventHandler ErrorHandle;

        private EventHandler<FileTransfer_BaseEventArgs> TransferActivity;

        protected void OnFileTransRequestReceive(FileTransfer_TransferRequest request, TcpClientEx tcpClient)
        {
            var desPath = BasePath;
            var isEnable = true;
            if (FileReceiveRequest!=null)
            {
                var e = new FileReceiveRequestEventArgs(request.TaskName, request.TransferType);

                e.FileSize = request.TotalFileLength;
                e.BasePath = this.BasePath;
                e.TargetFolderName = request.TargetFolderName;
                e.TaskName = request.TaskName;
                e.IsEnabled = true;
                e.TransportType = request.TransferType;


                FileReceiveRequest(this, e);
                isEnable = e.IsEnabled;
                if (e.IsEnabled==false)
                {
                    //TODO:拒绝传输
                }
                else
                {
                    desPath = e.BasePath;
                }
            }

            //if (FileReceiveRequest == null)
            //{

            //}
            //else
            //{
            try
            {
                //var e = new FileReceiveRequestEventArgs(request.TaskName, request.TransferType);

                //e.FileSize = request.TotalFileLength;
                //e.BasePath = this.BasePath;
                //e.IsEnabled = true;
                //FileReceiveRequest(this, e);


                if (isEnable==true && !desPath.IsNullOrEmpty())
                {
                    var temp = new FileTransferReceiveTask();
                    temp.MainSessionID = request.MainSessionID;
                    temp.FileCount = request.FileCount;
                    temp.BasePath = desPath;
                    temp.IsCancel = false;
                    temp.SrcTcp = tcpClient;

                    if (request.FileInfos.Length != 0)
                    {
                        var dic = new Dictionary<int, FileTransferReceiveFileBlock>();

                        foreach (var fileInfo in request.FileInfos)
                        {
                            var fileName = fileInfo.FileName;

                            if (request.TransferType==TransferType.File)
                            {
                                fileName = Path.Combine(desPath, Path.GetFileName(request.TargetFolderName));
                            }
                            else
                            {
                                fileName = Path.Combine(desPath, fileInfo.FileName);
                            }

                            dic.Add(fileInfo.FileID,
                                    new FileTransferReceiveFileBlock()
                                        {
                                            FileName = fileName,
                                            FileSize = fileInfo.FileSize,
                                            SubSessionID = fileInfo.FileID,
                                            LockerObj = new object(),
                                            ReceiveCount = 0
                                        });
                        }

                        temp.FileBlocks = dic;
                    }

                    cacheConfig.Add(request.MainSessionID, temp);
                }

                var t = new FileTransfer_TransferResponse();

                t.MainSessionID = request.MainSessionID;
                t.IsEnableTransfer = isEnable;

                var dt = t.ToBytes();

                tcpClient.Send(dt.Array, dt.Offset, dt.Count);

                if (!isEnable)
                {
                    tcpClient.Close();
                }
            }
            catch (Exception)
            {

            }
            finally
            {
                //tcpClient.Close();
            }
            //}
        }

        //protected void OnFileDataBlockResponseReceive(TransferBlockResponse dataBlock, TcpClientEx tcpClient)
        //{
        //    if (!cacheConfig.ContainsKey(dataBlock.MainSessionID))
        //    {
        //        return;
        //    }

        //    var mainConfig = cacheConfig[dataBlock.MainSessionID];

        //    if (!mainConfig.FileBlocks.ContainsKey(dataBlock.FileID))
        //    {
        //        return;
        //    }

        //    var config = mainConfig.FileBlocks[dataBlock.FileID];

        //    lock (config.LockerObj)
        //    {
        //        config.TargetSteam.Position = dataBlock.Offset;
        //        config.TargetSteam.Write(dataBlock.Data.Array, dataBlock.Data.Offset, dataBlock.Data.Count);

        //        Interlocked.Add(ref mainConfig.TotalReceiveLength, dataBlock.Data.Count);
        //        Interlocked.Add(ref config.ReceiveCount, dataBlock.Data.Count);

        //    }

        //    //TODO:接收数据后的应答

        //    if (config.ReceiveCount >= config.FileSize)
        //    {
        //        Interlocked.Add(ref mainConfig.FileReceiveCompletedCount, 1);

        //        config.TargetSteam.Close();
        //        config.TargetSteam.Dispose();
        //        mainConfig.FileBlocks.Remove(dataBlock.FileID);
        //    }

        //    if (mainConfig.FileCount == mainConfig.FileReceiveCompletedCount)
        //    {
        //        cacheConfig.Remove(mainConfig.MainSessionID);
        //        foreach (var block in mainConfig.FileBlocks)
        //        {
        //            block.Value.TcpClient.Close();
        //        }
        //    }
        //}

        private void OnFileTransferBegin(object sender, FileTransferBeginEventArgs e)
        {
            var au = new AutoResetEvent(false);

            if (!cacheConfig.ContainsKey(e.Data.MainSessionID))
            {
                return;
            }

            var mainConfig = cacheConfig[e.Data.MainSessionID];

            FileTransfer_DataBlockResponse ea = null;
            var currentSessionID = 0;
            var currentFileOffset = 0;


            this.FileDataBlockResponseReceive += (s, e1) =>
                                                 {
                                                     ea = null;
                                                     if (e1.Data.MainSessionID == mainConfig.MainSessionID && currentSessionID != 0 && currentSessionID == e1.Data.FileID && e1.Data.Offset == currentFileOffset)
                                                     {
                                                         ea = e1.Data;
                                                     }
                                                     au.Set();
                                                 };

            foreach (var fileBlock in mainConfig.FileBlocks)
            {
                if (mainConfig.IsCancel)
                {
                    break;
                }

                currentSessionID = 0;

                var fileConfig = fileBlock.Value;
                var errorCount = 0;

                Interlocked.Exchange(ref currentFileOffset, 0);
                Interlocked.Exchange(ref currentSessionID, fileConfig.SubSessionID);

                if (fileConfig.TargetSteam == null)
                {
                    //string desPath;
                    //string desFilePath;
                    
                    //if (Path.GetDirectoryName(fileConfig.FileName)==mainConfig.BasePath)
                    //{
                    //    desPath = mainConfig.BasePath;
                    //}
                    //else
                    //{
                    //    desPath = Path.GetDirectoryName(fileConfig.FileName);
                    //}

                    //if (desPath.IsNullOrEmpty())
                    //{
                    //    desPath=Path.Combine(mainConfig.BasePath, fileConfig.FileName);
                    //}

                    //if (desPath.IsNullOrEmpty())
                    //{
                    //    break;
                    //}

                    FileManager.CreateDirectory(Path.GetDirectoryName(fileConfig.FileName));

                    if (File.Exists(fileConfig.FileName))
                    {
                        File.Delete(fileConfig.FileName);
                    }

                    fileConfig.TargetSteam = new FileStream(fileConfig.FileName,
                                                            FileMode.CreateNew, FileAccess.ReadWrite);

                    if (fileConfig.TargetSteam==null)
                    {
                        break;
                    }
                }

                while (fileConfig.FileSize - currentFileOffset > 0)
                {
                    var p = new FileTransfer_DataBlockRequest();

                    p.MainSessionID = mainConfig.MainSessionID;
                    p.FileID = fileConfig.SubSessionID;
                    p.GetOffset = currentFileOffset;
                    p.GetCount = Math.Min(DataRevBlockCount, fileConfig.FileSize - currentFileOffset);

                    e.SrcTcp.Send(p.ToBytes());

                    au.WaitOne(5000);

                    if (ea == null || ea.Data.Count <= 0)
                    {
                        Interlocked.Increment(ref errorCount);
                    }
                    else
                    {
                        lock (fileConfig.LockerObj)
                        {
                            fileConfig.TargetSteam.Position = currentFileOffset;
                            fileConfig.TargetSteam.Write(ea.Data.Array, ea.Data.Offset, ea.Data.Count);

                            Interlocked.Add(ref mainConfig.TotalReceiveLength, ea.Data.Count);
                            Interlocked.Add(ref fileConfig.ReceiveCount, ea.Data.Count);

                            Interlocked.Add(ref currentFileOffset, ea.Data.Count);

                            
                        }                        
                    }

                    Interlocked.Exchange(ref ea, null);

                    if (p.GetCount < DataRevBlockCount || errorCount > 5)
                    {
                        var transferFileCompleted = new FileTransfer_SingleFileCompletedRequest()
                                                        {
                                                                FileID = currentSessionID,
                                                                MainSessionID = mainConfig.MainSessionID
                                                        };

                        Exception error = null;

                        try
                        {
                            fileConfig.TargetSteam.Flush();
                            fileConfig.TargetSteam.Close();
                            fileConfig.TargetSteam.Dispose();

                            e.SrcTcp.Send(transferFileCompleted.ToBytes());
                        }
                        catch (Exception ex)
                        {
                            error = ex;
                        }
                        finally
                        {
                            if (errorCount>5 && error!=null)
                            {
                                error=new Exception("错误次数超过5次");
                            }
                            OnTransferSingleEnd(mainConfig.MainSessionID, currentSessionID, error);
                            //OnTransferCompleted();                            
                        }

                        break;
                    }

                    Thread.Sleep(50);
                }

                if (mainConfig.IsCancel)
                {
                    break;
                }
            }

            Exception error1 = null;
            try
            {
                mainConfig.SrcTcp.Send((new FileTransfer_AllFileCompletedRequest() { MainSessionID = mainConfig.MainSessionID }).ToBytes());
            }
            catch (Exception ex)
            {
                error1 = ex;
            }
            finally
            {
                if (mainConfig.IsCancel)
                {
                    error1=new Exception("用户取消传输");
                }

                OnTransferEnd(mainConfig.MainSessionID, error1);
            }
        }

        private void OnFileTransferAllFileCompletedResponse(FileTransfer_AllFileCompletedResponse response, TcpClientEx tcpClient)
        {
            if (!cacheConfig.ContainsKey(response.MainSessionID))
            {
                return;
            }

            var mainConfig = cacheConfig[response.MainSessionID];

            

            tcpClient.Close();
           
            //tcpClient.Send(temp.tobytes);
        }

        private void OnFileTransferCancelRequest(FileTransfer_TransferCancelRequest request,TcpClientEx tcpClient)
        {
            if (!cacheConfig.ContainsKey(request.MainSessionID))
            {
                return;
            }

            var mainConfig = cacheConfig[request.MainSessionID];

            mainConfig.IsCancel = true;

            foreach (var fileBlock in mainConfig.FileBlocks)
            {
                lock (fileBlock.Value.LockerObj)
                {
                    fileBlock.Value.TargetSteam.Close();
                    fileBlock.Value.TargetSteam.Dispose();                    
                }
            }
            
            var temp = new FileTransfer_TransferCancelResponse();

            temp.MainSessionID = request.MainSessionID;

            //tcpClient.Send(temp.tobytes);
        }

        //private void OnTransferCompleted(int mainSessionID,int fileID,Exception error)
        //{
        //    if (!cacheConfig.ContainsKey(mainSessionID))
        //    {
        //        return;
        //    }

        //    var mainConfig = cacheConfig[mainSessionID];

        //    if (fileID!=0 && !mainConfig.FileBlocks.ContainsKey(fileID))
        //    {
        //        return;
        //    }

        //    string fileName = string.Empty;

        //    if (fileID!=0)
        //    {
        //        fileName = mainConfig.FileBlocks[fileID].FileName;
        //    }

        //    Events.EventHelper.RaiseAsync(TransferCompleted, this, new FileTransfer_TransferCompletedEventArgs(mainConfig.TaskName, fileName, error));

        //}

        private void OnTransferActivityRequest(FileTransfer_TransferActivityRequest request,TcpClientEx tcpClient)
        {
            if (!cacheConfig.ContainsKey(request.MainSessionID))
            {
                return;
            }

            var temp = new FileTransfer_TransferActivityResponse();

            temp.MainSessionID = request.MainSessionID;

            tcpClient.Send(temp.ToBytes());
        }

        private void OnTransferActivityResponse(FileTransfer_TransferActivityResponse response)
        {
            if (!cacheConfig.ContainsKey(response.MainSessionID))
            {
                return;
            }

            var mainConfig = cacheConfig[response.MainSessionID];

            Events.EventHelper.RaiseAsync(TransferActivity, this, new FileTransfer_BaseEventArgs(response.MainSessionID, mainConfig.TaskName));

        }

        private void OnTransferSingleEnd(int mainConfigID, int subSessionID, Exception error)
        {
            FileTransferReceiveTask mainConfig = null;

            lock (cacheConfig)
            {
                if (!cacheConfig.ContainsKey(mainConfigID))
                {
                    return;
                }

                mainConfig = cacheConfig[mainConfigID];
            }

            if (!mainConfig.FileBlocks.ContainsKey(subSessionID))
            {
                return;
            }

            if (error != null)
            {
                OnTransferEnd(mainConfigID, error);
            }
            else
            {
                var fileblock = mainConfig.FileBlocks[subSessionID];

                Events.EventHelper.RaiseAsync(TransferSingleCompleted, this, new FileTransfer_FileCompletedEventArgs(mainConfigID, mainConfig.TaskName, fileblock.FileName, null));
            }
        }

        private void OnTransferEnd(int mainConfigID,Exception error)
        {
            FileTransferReceiveTask mainConfig = null;

            lock (cacheConfig)
            {
                if (!cacheConfig.ContainsKey(mainConfigID))
                {
                    return;
                }

                mainConfig = cacheConfig[mainConfigID];

                cacheConfig.Remove(mainConfigID);
            }

            mainConfig.SrcTcp.Close();

            foreach (var fileBlock in mainConfig.FileBlocks)
            {
                lock (fileBlock.Value.LockerObj)
                {
                    try
                    {
                        fileBlock.Value.TargetSteam.Close();
                        fileBlock.Value.TargetSteam.Dispose();   
                    }
                    catch (System.Exception)
                    {
                        continue;
                    }

                }
            }

            mainConfig.FileBlocks.Clear();

            

            Events.EventHelper.Raise(TransferAllCompleted, this, new FileTransfer_AllCompletedEventArgs(mainConfig.MainSessionID, mainConfig.TaskName, error));
        }

        void tcpClient_DataReceive(object sender, DataReceiveEventArgs e)
        {
            if (e.Data.Count <= 0)
            {
                return;
            }

            var cmdType = (CmdID)BitConverter.ToInt32(e.Data.Array, e.Data.Offset);

            try
            {
                switch (cmdType)
                {
                    case CmdID.FileTransfer_TransferRequest:
                        OnFileTransRequestReceive(FileTransfer_TransferRequest.Load(e.Data.Array, e.Data.Offset, e.Data.Count), e.SrcTcp);
                        break;
                    case CmdID.FileTransfer_DataBlockResponse:
                        //FileDataBlockResponseReceive(this,
                        //                             new FileDataBlockResponseEventArgs(
                        //                                     FileTransfer_DataBlockResponse.Load(e.Data.Array, e.Data.Offset,
                        //                                                                e.Data.Count), e.SrcTcp));
                        Events.EventHelper.Raise(FileDataBlockResponseReceive, this, new FileDataBlockResponseEventArgs(FileTransfer_DataBlockResponse.Load(e.Data.Array, e.Data.Offset, e.Data.Count), e.SrcTcp));
                        //OnFileDataBlockResponseReceive(TransferBlockGet.Load(e.Data.Array, e.Data.Offset, e.Data.Count), e.SrcTcp);
                        break;
                    case CmdID.FileTransfer_TransferBegin:
                        //OnFileTransferBegin(this, new FileTransferBeginEventArgs(FileTransfer_TransferBegin.Load(e.Data.Array, e.Data.Offset, e.Data.Count), e.SrcTcp));
                        Events.EventHelper.RaiseAsync(OnFileTransferBegin, this, new FileTransferBeginEventArgs(FileTransfer_TransferBegin.Load(e.Data.Array, e.Data.Offset, e.Data.Count), e.SrcTcp));
                        //OnFileTransferBegin(TransferBegin.Load(e.Data.Array, e.Data.Offset, e.Data.Count), e.SrcTcp);
                        break;
                    case CmdID.FileTransfer_TransferCancelRequest:

                        break;
                    case CmdID.FileTransfer_AllFileCompletedResponse:
                        OnFileTransferAllFileCompletedResponse(FileTransfer_AllFileCompletedResponse.Load(e.Data.Array,e.Data.Offset,e.Data.Count),
                                                               e.SrcTcp);
                        break;
                    case CmdID.FileTransfer_TransferActivityRequest:
                        OnTransferActivityRequest(FileTransfer_TransferActivityRequest.Load(e.Data.Array, e.Data.Offset,
                                                                                            e.Data.Count),e.SrcTcp);
                        break;
                    case CmdID.FileTransfer_TransferActivityResponse:
                        OnTransferActivityResponse(FileTransfer_TransferActivityResponse.Load(e.Data.Array, e.Data.Offset,
                                                                                            e.Data.Count));
                        break;
                }
            }
            catch (Exception)
            {

            }
        }

        private EventHandler<FileDataBlockResponseEventArgs> FileDataBlockResponseReceive;

        private EventHandler<FileTransferBeginEventArgs> FileTransferBeginReceive;

        void socket_acceptConnect(IAsyncResult iar)
        {
            Socket socket = null;

            try
            {
                socket = _socket.EndAccept(iar);
            }
            catch (System.Exception)
            {
                return;
            }

            var tcp = new TcpClientEx(socket, true);

            tcp.DataReceive += tcpClient_DataReceive;

            _socket.BeginAccept(socket_acceptConnect, null);
        }

        private void lineChecker(object state)
        {

            var au = new AutoResetEvent(false);
            var isSuccess = false;
            var currentSessionID = 0;

            EventHandler<FileTransfer_BaseEventArgs> handler = (s, e) =>
            {
                if (e.MainSessionID == currentSessionID)
                {
                    isSuccess = true;
                    au.Set();
                }

            };

            this.TransferActivity += handler;

            foreach (var task in cacheConfig)
            {
                var temp = new FileTransfer_TransferActivityRequest();

                temp.MainSessionID = task.Value.MainSessionID;

                currentSessionID = task.Value.MainSessionID;

                task.Value.SrcTcp.Send(temp.ToBytes());

                au.WaitOne(10000);

                if (!isSuccess)
                {
                    //Interlocked.Increment(ref offlineCount);

                    task.Value.OfflilneCount++;

                    var tempCount = task.Value.OfflilneCount;

                    if (tempCount > 5)
                    {
                        OnTransferEnd(currentSessionID, new FileTransferException_Offline());
                    }
                }
                else
                {
                    task.Value.OfflilneCount = 0;
                    //Interlocked.Exchange(ref offlineCount, 0);
                }                
            }

            this.TransferActivity -= handler;

        }

        class FileTransferBeginEventArgs : EventArgs
        {
            public FileTransferBeginEventArgs(FileTransfer_TransferBegin data, TcpClientEx srcTcp)
            {
                Data = data;
                SrcTcp = srcTcp;
            }

            public FileTransfer_TransferBegin Data;
            public TcpClientEx SrcTcp;
        }

        class FileDataBlockResponseEventArgs : EventArgs
        {
            public FileDataBlockResponseEventArgs(FileTransfer_DataBlockResponse data, TcpClientEx srcTcp)
            {
                Data = data;
                SrcTcp = srcTcp;
            }

            public FileTransfer_DataBlockResponse Data;
            public TcpClientEx SrcTcp;
        }

    }

    public class FileTransfer_TransferCompletedEventArgs:EventArgs
    {
        public FileTransfer_TransferCompletedEventArgs(string taskName, string fileName):this(taskName,fileName,null)
        {
            
        }

        public FileTransfer_TransferCompletedEventArgs(string taskName, string fileName, Exception error)
        {
            TaskName = taskName;
            FileName = fileName;
            Error = error;
        }

        public string TaskName;
        public string FileName;

        public Exception Error;

        public bool HasError
        {
            get { return Error != null; }
        }
    }

    public class FileReceiveRequestEventArgs : EventArgs
    {
        public FileReceiveRequestEventArgs(string taskName, TransferType transportType)
        {
            TaskName = taskName;
            IsEnabled = true;
            this.TransportType = transportType;
        }

        public string TaskName;
        public long FileSize;
        public bool IsEnabled;
        public TransferType TransportType;
        public string TargetFolderName;
        public string BasePath;
    }

    public class FileTransferReceiveTask
    {
        public FileTransferReceiveTask()
        {
            FileBlocks = new Dictionary<int, FileTransferReceiveFileBlock>();
        }

        public string TaskName;
        public int MainSessionID;
        public int FileCount;
        public int FileReceiveCompletedCount;
        public string BasePath;
        public long TotalFileLength;
        public long TotalReceiveLength;
        public bool IsCancel;
        public Dictionary<int, FileTransferReceiveFileBlock> FileBlocks;
        public TcpClientEx SrcTcp;
        internal int OfflilneCount ;
    }

    public class FileTransferReceiveFileBlock
    {
        public FileTransferReceiveFileBlock()
        {
            LockerObj = new object();
        }

        public int SubSessionID;
        public Stream TargetSteam;
        public int ReceiveCount;
        public string FileName;
        public int FileSize;
        public int CRCCode;
        public TcpClientEx TcpClient;
        public object LockerObj;
    }
}
